---
title: Agent State
icon: "lucide/Bot"
description: Render the state of your agent with custom UI components.
---
import { Accordions, Accordion } from "fumadocs-ui/components/accordion";
import { IframeSwitcher } from "@/components/content"
import { Tabs, Tab } from "fumadocs-ui/components/tabs"
import RunAndConnect from "@/snippets/integrations/pydantic-ai/run-and-connect.mdx"

<IframeSwitcher
  id="agent-state-example"
  exampleUrl="https://feature-viewer.copilotkit.ai/langgraph/feature/agentic_generative_ui?sidebar=false&chatDefaultOpen=false"
  codeUrl="https://feature-viewer.copilotkit.ai/langgraph/feature/agentic_generative_ui?view=code&sidebar=false&codeLayout=tabs"
  exampleLabel="Demo"
  codeLabel="Code"
  height="700px"
/>

## What is this?

All Pydantic AI Agents are stateful. This means that as your agent progresses through nodes, a state object is passed between them perserving
the overall state of a session. CopilotKit allows you to render this state in your application with custom UI components, which we call **Agentic Generative UI**.

## When should I use this?

Rendering the state of your agent in the UI is useful when you want to provide the user with feedback about the overall state of a session. A great example of this
is a situation where a user and an agent are working together to solve a problem. The agent can store a draft in its state which is then rendered in the UI.

## Implementation

<Steps>
  <Step>
    ### Run and connect your agent
    <RunAndConnect components={props.components} />
  </Step>
  <Step>
    ### Set up your agent with state

    Create your Pydantic AI agent with a stateful structure. Here's a complete example that tracks searches:

    ```python title="agent.py"
    import asyncio
    from textwrap import dedent
    from pydantic import BaseModel, Field
    from pydantic_ai import Agent, RunContext
    from pydantic_ai.ag_ui import StateDeps
    from ag_ui.core import StateSnapshotEvent, EventType


    class Search(BaseModel):
        query: str
        done: bool


    class AgentState(BaseModel):
        searches: list[Search] = Field(default_factory=list)


    agent = Agent("openai:gpt-4o-mini", deps_type=StateDeps[AgentState])


    @agent.tool
    async def add_search(
        ctx: RunContext[StateDeps[AgentState]], new_query: str
    ) -> StateSnapshotEvent:
        """Add a search to the agent's list of searches."""
        new_search = Search(query=new_query, done=False)
        searches = ctx.deps.state.searches
        searches.append(new_search)
        agent_state = AgentState(searches=searches)
        ctx.deps.state = agent_state

        return StateSnapshotEvent(type=EventType.STATE_SNAPSHOT, snapshot=agent_state)


    @agent.tool
    async def run_searches(ctx: RunContext[StateDeps[AgentState]]) -> StateSnapshotEvent:
        """Run the searches in the agent's state."""
        searches = ctx.deps.state.searches

        for search in searches:
            await asyncio.sleep(1)
            search.done = True

        agent_state = AgentState(searches=searches)
        ctx.deps.state = agent_state

        return StateSnapshotEvent(type=EventType.STATE_SNAPSHOT, snapshot=agent_state)


    @agent.instructions()
    async def search_instructions(ctx: RunContext[StateDeps[AgentState]]) -> str:
        """Instructions for the search agent."""
        return dedent(
            f"""
            You are a helpful assistant for storing searches.

            IMPORTANT:
            - Use the `add_search` tool to add a search to the agent's state
            - After using the `add_search` tool, YOU MUST ALWAYS use the `run_searches` tool to run the searches
            - ONLY USE THE `add_search` TOOL ONCE FOR A GIVEN QUERY

            Current searches:
            {ctx.deps.state.model_dump_json(indent=2)}
            """
        )


    app = agent.to_ag_ui(deps=StateDeps(AgentState()))

    if __name__ == "__main__":
        import uvicorn
        uvicorn.run(app, host="0.0.0.0", port=8000)
    ```

  </Step>
  <Step>
    ### Render state of the agent in the chat
    Now we can utilize `useCoAgentStateRender` to render the state of our agent **in the chat**.

    ```tsx title="app/page.tsx"
    // ...
    import { useCoAgentStateRender } from "@copilotkit/react-core";
    // ...

    // Define the state of the agent, should match the state of your Pydantic AI Agent.
    type AgentState = {
      searches: {
        query: string;
        done: boolean;
      }[];
    };

    function YourMainContent() {
      // ...

      // [!code highlight:13]
      // styles omitted for brevity
      useCoAgentStateRender<AgentState>({
        name: "my_agent", // MUST match the agent name in CopilotRuntime
        render: ({ state }) => (
          <div>
            {state.searches?.map((search, index) => (
              <div key={index}>
                {search.done ? "✅" : "❌"} {search.query}{search.done ? "" : "..."}
              </div>
            ))}
          </div>
        ),
      });

      // ...

      return <div>...</div>;
    }
    ```

    <Callout type="warn" title="Important">
      The `name` parameter must exactly match the agent name you defined in your CopilotRuntime configuration (e.g., `my_agent` from the quickstart).
    </Callout>

  </Step>
  <Step>
    ### Render state outside of the chat
    You can also render the state of your agent **outside of the chat**. This is useful when you want to render the state of your agent anywhere
    other than the chat.

    ```tsx title="app/page.tsx"
    import { useCoAgent } from "@copilotkit/react-core"; // [!code highlight]
    // ...

    // Define the state of the agent, should match the state of your Pydantic AI Agent.
    type AgentState = {
      searches: {
        query: string;
        done: boolean;
      }[];
    };

    function YourMainContent() {
      // ...

      // [!code highlight:3]
      const { state } = useCoAgent<AgentState>({
        name: "my_agent", // MUST match the agent name in CopilotRuntime
      })

      // ...

      return (
        <div>
          {/* ... */}
          <div className="flex flex-col gap-2 mt-4">
            {/* [!code highlight:5] */}
            {state.searches?.map((search, index) => (
              <div key={index} className="flex flex-row">
                {search.done ? "✅" : "❌"} {search.query}
              </div>
            ))}
          </div>
        </div>
      )
    }
    ```

    <Callout type="warn" title="Important">
      The `name` parameter must exactly match the agent name you defined in your CopilotRuntime configuration (e.g., `my_agent` from the quickstart).
    </Callout>

  </Step>
  <Step>
    ### Give it a try!

    You've now created a component that will render the agent's state in the chat.

    <video
      src="https://cdn.copilotkit.ai/docs/copilotkit/images/coagents/agentic-generative-ui.mp4"
      className="rounded-lg shadow-xl"
      loop
      playsInline
      controls
      autoPlay
      muted
    />
  </Step>
</Steps>
